How To Add Proxy Support To An Existing Service (In Docker Compose)
有的服务本身没有支持代理,我们在需要的时候应当怎么(在使用时)为它们添加代理呢? 这并不是一个完全假设的情况,例如 Home Assistant 就是个例子。 这里我们的讨论当然把服务作为黑盒考虑,不然相当于改变了问题的前提。
通常我们能想到的做法是在 OS 层面比如 iptables 或者在网络层面比如路由器上配置相应的流量规则,但是这些做法都比较“靠外”,影响范围较大也有较高的权限要求。
如果这个服务是部署在一个 Docker Compose 中的,那我们其实有更简单轻量的做法:在这个 Docker Comopse 中添加 dcdn 服务并使其承接本来的这个服务的特定流量。
具体来说就是在原来的 compose.yaml
文件中加入如下的改动(假定 app
就是本来的服务):
services: dcdn: image: ghcr.io/freebirdljj/dcdn:latest environment: - all_proxy=${ALL_PROXY} expose: - "80" - "443" app: ... links: - dcdn:domain1 - dcdn:domain2 ...
这里的 domain1
, domain2
等就是具体的域名,比如 github.com
。
我们先来解释一下比如某个域名 domain1
在这个新的 Docker Compose 里 app
服务是怎么请求的:
首先对于 app
服务而言 domain1
在 links
中, 所以虽然我们知道 domain1
在公网上是有一个具体的服务器的,但是 app
服务仍然会在 Docker Compose 的指引下找到 dcdn
服务并认为后者就是 domain1
的服务器。
这样这个原始的请求就被很自然地发送给了 dcdn
服务。
并且由于对于 app
服务而言 domain1
的 DNS 解析结果是 Docker Compose 给出的,所以和公网上的 domain1
的 DNS 解析结果没有任何关系,也不会有 ESNI 或 ECH 等事了,这就让 dcdn
得以嗅探出 HTTP/HTTPS 请求的目标。
对于 HTTP 请求,目标地址可以通过读取请求的 Host
header 获取;而对于 HTTPS 请求,目标地址可以通过读取 TLS ClientHello 消息的 ServerName
获取。
dcdn
一旦嗅探出了目标地址,就会将整个连接进行转发,后面的事就自然而然了。
需要注意的是 app
服务的 links
只会影响 app
服务本身,而不会影响 dcdn
对 domain1
, domain2
等地址的解析和请求的。
简单来说就是在这个 Docker Compose 中 app
服务误以为 dcdn
就是它要访问的 domain1
, domain2
等域名的服务器并且其 TLS 支持不带服务端匿名性。
dcdn
收到请求后会进行嗅探并进行连接级别的转发,这里虽然 dcdn
转发的请求仍然缺失 ESNI/ECH 等特性,但有理由相信 $ALL_PROXY
指向的代理应当能够很大程度缓解这一问题。
有两点值得提一下:
- 可以直接用一个 HTTP 代理取代
dcdn
么? 这是不行的,因为一个 HTTP 请求如果要被代理,本身需要进行一定的修改,反过来说原始的请求 HTTP 代理则无法正确直接处理(不然请求也不用修改了)。 - 为什么
dcdn
是在连接层面进行转发而不是在请求层面? 主要是因为对于 HTTPS 请求所基于的 TLS 连接是必须整体进行转发的。